#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import os
import re
import uuid
import signal
import calendar
import logging
import sys
import weakref
import functools
import socket
import struct
import time
import math
import fcntl
import errno
import threading
import traceback
import subprocess
import urllib
import httplib
import urllib2
import json
import datetime
import ConfigParser

import Ump

from Ump.common.errcode import SYSTEM_CODE_MAP, CUSTOM_CODE_MAP
from Ump.common import threadpool
from Ump.lib import jsonobject
from Ump import defs

LOG = logging.getLogger('Ump.utils')

LOCAL_HOST = ['0.0.0.0', 'localhost', '127.0.0.1']


ump_home = defs.UMP_HOME
install_path = defs.get_install_dir()


xx = lambda x: '%.2f'%(float(x)) if x is not None else 0
yy = lambda x, y: math.ceil(x/float('%.f'%(y))) if x is not None else 0
byte2GB = lambda byte, unit=1024: float(byte) /unit /unit /unit

class LogConfig(object):
    instance = None

    LOG_FOLER = defs.LOG_FOLER

    def __init__(self):
        if not os.path.exists(self.LOG_FOLER):
            os.makedirs(self.LOG_FOLER, 0755)
        self.log_path = os.path.join(self.LOG_FOLER, 'agent_server.log')
        self.log_level = logging.DEBUG
        self.log_to_console = True

    def set_log_to_console(self, to_console):
        self.log_to_console = to_console

    def get_log_path(self):
        return self.log_path

    def set_log_path(self, path):
        self.log_path = path

    def set_log_level(self, level):
        self.log_level = level

    def configure(self):
        dirname = os.path.dirname(self.log_path)
        if not os.path.exists(dirname):
            os.makedirs(dirname, 0755)
        logging.basicConfig(filename=self.log_path, level=self.log_level,format="%(asctime)s %(levelname)s [%(name)s] %(message)s")

    def get_logger(self, name, logfd=None):
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        max_rotate_handler = logging.handlers.RotatingFileHandler(self.log_path, maxBytes=10*1024*1024, backupCount=3)
        logger.addHandler(max_rotate_handler)

        if self.log_to_console:
            formatter = logging.Formatter('%(asctime)s %(levelname)s [%(name)s] %(message)s')
            if not logfd:
                logfd = sys.stdout
            ch = logging.StreamHandler(logfd)
            ch.setLevel(logging.DEBUG)
            ch.setFormatter(formatter)
            logger.addHandler(ch)
        return logger

    @staticmethod
    def get_log_config():
        if not LogConfig.instance:
            LogConfig.instance = LogConfig()
        return LogConfig.instance

def configure_log(log_path, level=logging.INFO, log_to_console=True):
    cfg = LogConfig.get_log_config()
    log_dir = os.path.dirname(log_path)
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)
    cfg.set_log_path(log_path)
    cfg.set_log_level(level)
    cfg.set_log_to_console(log_to_console)
    cfg.configure()

def get_logger(name, logfd=None):
    return LogConfig.get_log_config().get_logger(name, logfd)

def get_exception_stacktrace():
    return traceback.format_exc()

def abs_path(path):
    if path.startswith('~'):
        return os.path.expanduser(path)
    else:
        return os.path.abspath(path)

def strip(s):
    return s.strip(" \n\t\r")

def myprint(*args, **kwargs):
    LOG.info('=='*30)
    LOG.info(args, kwargs)
    LOG.info('=='*30)

def uuid4():
    return str(uuid.uuid4()).replace('-', '')

class PropertyFile(object):
    def __init__(self, path):
        self.path = abs_path(path)
        self.properties = {}

        if not os.path.isfile(self.path):
            raise Exception("can not find %s or it's not file" % self.path)

        with open(self.path, 'r') as fd:
            content = fd.read()
            lines = content.split("\n")
            for line in lines:
                line = strip(line)
                if not line: continue
                if line.startswith("#"): continue

                try:
                    key, value = line.split("=", 1)
                    self.properties[key] = value
                except Exception:
                    raise Exception("invalid line %s: %s" % (lines.index(line), line))

    def get(self, key, default=None):
        return self.properties.get(key, default)

    def get_raise_error_on_none(self, key):
        val = self.get(key)
        if not val:
            raise Exception("can not find property[%s] in file[%s]" % (key, self.path))

_internal_lock = threading.RLock()
_locks = weakref.WeakValueDictionary()

def _get_lock(name):
    with _internal_lock:
        lock = _locks.get(name, threading.RLock())
        if not name in _locks:
            _locks[name] = lock
        return lock

class NamedLock(object):
    def __init__(self, name):
        self.name = name
        self.lock = None

    def __enter__(self):
        self.lock = _get_lock(self.name)
        self.lock.acquire()
        #logger.debug('%s got lock %s' % (threading.current_thread().name, self.name))

    def __exit__(self, type, value, traceback):
        self.lock.release()
        #logger.debug('%s released lock %s' % (threading.current_thread().name, self.name))


def lock(name='defaultLock'):
    def wrap(f):
        @functools.wraps(f)
        def inner(*args, **kwargs):
            with NamedLock(name):
                retval = f(*args, **kwargs)
            return retval
        return inner
    return wrap


def alarm_handler(signum, frame):
    raise Exception("local command execute time out")


def _exec_pipe(cmd, retry = 3, p = False, timeout = 0, is_raise=False):
    env = {"LANG" : "en_US", "LC_ALL" : "en_US", "PATH" : os.getenv("PATH")}
    #cmd = self.lich_inspect + " --movechunk '%s' %s  --async" % (k, loc)
    _retry = 0
    cmd1 = ''
    for i in cmd:
        cmd1 = cmd1 + i + ' '
    if (p):
        LOG.info(cmd1)
    while (1):
        p = None
        try:
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env = env)
        except Exception, e:
            raise Exception(e)

        if timeout != 0:
            signal.signal(signal.SIGALRM, alarm_handler)
            signal.alarm(timeout)
        try:
            stdout, stderr = p.communicate()
            signal.alarm(0)
            ret = p.returncode
            if (ret == 0):
                return ret,stdout,stderr
            elif (ret == errno.EAGAIN and _retry < retry):
                _retry = _retry + 1
                time.sleep(1)
                continue
            else:
                if is_raise:
                    raise Exception(stderr)
                return ret, stdout, stderr

        except KeyboardInterrupt as err:
            p.kill()
            raise Exception(err)
            exit(errno.EINTR)


def get_ip_address(eth_device):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', eth_device[:15])
    )[20:24])


def get_eth_device():
    recode, stdout, stderr = _exec_pipe("ipmaddr |grep -v inet |grep -v link |grep -v lo|awk '{print $2}'")
    eth_devices = stdout.split()
    return eth_devices


def _system_ip():
    eth_devices = get_eth_device()
    result = []
    vswitch = None
    if isinstance(eth_devices,list):
        for eth in eth_devices:
            try:
                ip = get_ip_address(eth)
                result.append(ip)
            except Exception,e:
                LOG.info(e)
                pass
    return result


def get_server_ip(netmask=None):
    manager_ip = None
    #get ip from ump.conf
    from Ump.common import config

    if config.ui_server_ip and not (config.ui_server_ip in LOCAL_HOST):
        manager_ip = config.ui_server_ip
    else:
        #get ip from env
        try:
            manager_ip = os.environ['ui_server_ip']
        except:
            pass

	addresses = local_addresses() 
	for address in addresses.values():
            add_mask = ".".join(address.split('.')[0:3])
	    netmask = ".".join(netmask.split('.')[0:3])
	    if add_mask == netmask:
	        return address 
	

        if not manager_ip or manager_ip in LOCAL_HOST:
            # get ip from default via interface
            try:
                cmd = """dev=`ip route|grep default|awk '{print $5}'`; ip addr show $dev| grep "inet" | awk '{print $2}'| head -n 1 | awk -F '/' '{print $1}'"""
                recode, stdout, stderr = _exec_pipe(cmd)
                for str in stdout.split("\n"):
                    manager_ip = str
                    break
            except Exception as e:
                LOG.info(e)

    if not manager_ip:
        raise Exception('not get controller ip')
    
    return manager_ip


def local_addresses():
    eths = get_eth_device()
    res = {}
    if isinstance(eths, list):
        for eth in eths:
            try:
                address = get_ip_address(eth)
                res[eth] = address
            except Exception,e:
                pass
    return res


def get_manager_ip():
    addresses_map = local_addresses()
    addresses = addresses_map.values()

    vswitch = addresses_map.get('vswitch')
    addresses_str = ",".join(addresses)
 
    recode, stdout, stderr = _exec_pipe("hostname")
    hostname = stdout.strip() 
    return addresses_str, hostname, get_server_ip 


def ensure_dir(dir_f):
    dir_ = os.path.dirname(dir_f)
    if not os.path.isdir(dir_):
        LOG.info("*** make dir: %s" % dir_)
        os.makedirs(dir_)


def make_dir(dir_):
    if not os.path.isdir(dir_):
        LOG.info("*** make dir: %s" % dir_)
        os.makedirs(dir_)


def _lock_file(key, timeout=3600):
    ensure_dir(key)
    key = os.path.abspath(key)

    lock_fd = None
    while (1):
        LOG.info("lock " + key)
        lock_fd = open(key, 'a')

        try:
            fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        except IOError as err:
            if err.errno == errno.EAGAIN:
                fd = open(key, 'r')
                s = fd.read()
                fd.close()
                if (',' in s):
                    (ltime, pid) = s.split(',')
                    ltime = int(ltime)
                    pid = int(pid)
                    now = int(time.time())
                    if (now - ltime > timeout):
                        msgs = "%s locked by %d time %u, just kill it" % (key, pid, now - ltime)
                        print >>sys.stderr, msgs
                        os.system("kill -9 %d" % pid)
                        lock_fd.close()
                        os.unlink(key)
                        continue
                    else:
                        LOG.info("%s locked, exit..." % (key))
                        print "%s locked, exit..." % (key)
                        exit(errno.EBUSY)
                else:
                    os.unlink(key)
                    lock_fd.close()
                    continue
            else:
                os.unlink(key)
                raise

        lock_fd.truncate(0)
        s = str(int(time.time())) + ',' + str(os.getpid())
        lock_fd.write(s)
        lock_fd.flush()
        break
    return lock_fd

def _unlock_file(lock_fd):
    fcntl.flock(lock_fd.fileno(), fcntl.LOCK_UN)

def _lock_file1(key, timeout=1, p=False):
    key = os.path.abspath(key)
    parent = os.path.split(key)[0]
    os.system("mkdir -p " + parent)


    if p:
        LOG.info("get lock %s" %(key))
    lock_fd = open(key, 'a')

    if timeout != 0:
        signal.signal(signal.SIGALRM, alarm_handler)
        signal.alarm(timeout)
    try:
        fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX)
        signal.alarm(0)
        if p:
            LOG.info("lock %s success" %(key))
    except Exception, e:
        raise Exception("lock %s failed" %(key))

    return lock_fd

def _unlock_file1(lock_fd):
    fcntl.flock(lock_fd.fileno(), fcntl.LOCK_UN)

def _str2dict(s):
    if (s[-1] == '\n'):
        s = s[:-1]

    a = s.split('\n')
    d = {}
    a = [i for i in a if i]
    for i in a:
        p = i.split(':')
        if (d.get(p[0])):
            raise  Exception("dup key exist")
        try:
            d[p[0].strip()] = p[1].strip()
        except IndexError as err:
            LOG.info("str %s" % (s))
            raise
    return d

def check_network(networks, ip):
    network = networks.split('.')[:3]
    network = '.'.join(network)

    res = ip.split('.')[:3]
    res = '.'.join(res)
    return res == network 


def get_dates(timestamp, num_days):
    enddate =  datetime.date.fromtimestamp(float(timestamp))
    dates = []
    days = range(1, num_days)
    days.reverse()
    for count in days:
        delta_date = enddate - datetime.timedelta(days=count)
        dates.append(str(delta_date))
    dates.append(str(enddate))
    return dates

def get_week(timestamp):
    return get_dates(timestamp, 7)

def get_month(timestamp):
    return get_dates(timestamp, 31)

def conv_float(x, decimal=2):
    #只保留2位，不进位
    assert(isinstance(x, float))
    a, b = str(x).split('.')
    return float(a + '.' + b[:decimal])


def parse_int(x):
    try:
        x = x.strip()
        res = int(x)
    except Exception, e:
        res = x 
    return res 


def errcode_to_tip(recode):
    recode = recode.strip()
    res_tip = ""
    if not recode:
        res_tip = u"未知错误"
    recode = parse_int(recode)
    res_tip = SYSTEM_CODE_MAP.get(recode, res_tip)
    if res_tip is None:
        res_tip = CUSTOM_CODE_MAP.get(recode, res_tip)

    if res_tip :
        return res_tip
    else:
        res_tip = u"错误码：%s" %recode

    return res_tip

def get_dbconfig():
    config = ConfigParser.RawConfigParser()
    db_cfg_path = '%s/etc/ump' % install_path
    config.read(os.path.join(db_cfg_path, 'db.cfg'))
    dbn, user, pw, db = None, None, None, None
    if config.has_section('sqlite'):
        name = config.get('sqlite', 'name')
        dbname = name.split('///')[-1]
        db = web.database(dbn='sqlite',db=dbname)
    if config.has_section('mysql'):
        dbn = 'mysql'
        user = config.get('mysql', 'user')
        pw = config.get('mysql', 'password')
        dbname = config.get('mysql', 'database')
        db = web.database(dbn='mysql', db=dbname, user=user, pw=pw)
    return db


def _run_with_threadpool(func, args, threadnum=5, callback=None):
    """Convenience wrappet to spawn multiple threads
    args:[((arg,), {})]
    """
    def exp_callback(request, exc_info):
        raise Exception(exc_info[1])

    pool = threadpool.ThreadPool(threadnum)
    for req in threadpool.makeRequests(func, args, callback, exp_callback):
        pool.putRequest(req)
    pool.wait()

def multi_exec(func, args, timeout = None, timeout_args = None):
    #args = [[arg1, ...], ...]
    ts = []
    for i in args:
        t = threading.Thread(target=func, args=i)
        t._args = i
        ts.append(t)

    [t.start() for t in ts]
    if timeout is None:
        [t.join() for t in ts]
    else:
        [t.join(timeout) for t in ts]

    for t in ts:
        if t.is_alive():
            timeout_args.append(t._args)

    return ts

def multithreading_with_return(func, args):
    def _exec_warp(func, arg, index, rs): 
        result = func(arg)
        rs.insert(index, result)

    ts = [] 
    rs = [] 
    for index, arg in enumerate(args):
        t = threading.Thread(target=_exec_warp, args=[func, arg, index,  rs]) 
        ts.append(t)

    [t.start() for t in ts]
    [t.join() for t in ts]
    return rs

def _random_free_port():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("",0))
    port = s.getsockname()[1]
    s.close()
    return port

def _exec_http_async(cmd, uuid, host='127.0.0.1', port=defs.AGENT_PORT, url="/exec_pipe", timeout=360):
    from Ump.common import config
    response = None
    rsp = None
    try:
        http_url = 'http://%s:%s%s' % (host, port, url)
        content = None
        if cmd is not None:
            content = jsonobject.dumps(cmd)

        request = urllib2.Request(http_url, content)
        request.add_header('Content-Type', 'application/json')
        if content is not None:
            request.add_header('Content-Length', str(len(content)))
        else:
            request.add_header('Content-Length', '0')
        request.add_header('taskuuid', uuid)
        request.add_header('callbackurl', 'http://%s:%s/callback' % (get_server_ip(host), config.controller_port))
        request.get_method = lambda: 'POST'

        rsp = urllib2.urlopen(request, timeout=timeout)

        response = rsp.read()

        if rsp.code != 200:
            raise Exception(rsp.reason)
    except Exception, e:
        # traceback.print_exc()
        if str(e).find('timed out') != -1:
            raise Exception('命令执行超时')
        raise e
    finally:
        if rsp:
            rsp.close()

    return response


def _exec_http(cmd, host='127.0.0.1', port=defs.AGENT_PORT, timeout=360, url="/exec_pipe"):
    response = None
    rsp = None
    try:
        http_url = 'http://%s:%s%s' % (host, port, url)
        content = None
        if cmd is not None:
            content = jsonobject.dumps(cmd)

        request = urllib2.Request(http_url, content)
        request.add_header('Content-Type', 'application/json')
        if content is not None:
            request.add_header('Content-Length', str(len(content)))
        else:
            request.add_header('Content-Length', '0')
#        request.add_header('taskuuid', uuid)
#        request.add_header('callbackurl', 'http://%s:%s/callback' % (get_server_ip(), config.controller_port))
        request.get_method = lambda: 'POST'

        rsp = urllib2.urlopen(request, timeout=timeout)
        response = rsp.read()
        response = jsonobject.loads(response)
        if rsp.code != 200:
            raise Exception(rsp.reason)
    except Exception, e:
        traceback.print_exc()
        if str(e).find('timed out') != -1:
            raise Exception('cmd timeout')
        raise e
    finally:
        if rsp:
            rsp.close()

    return response


def parse_str2list(s):
    if isinstance(s, str) or isinstance(s, unicode):
        s = [x for x in s.strip().split(',') if x]
    return s


def time_greater_than(refer_time, now=None, **kwargs):
    if not now:
        now = datetime.datetime.today()
    delta = datetime.timedelta(**kwargs)
    return now - refer_time  > delta


def size_to_gb(size): 
    if 'G' in size:
        size = int(float(size.strip('GB').strip('G')))
    elif 'M' in size:
        size = int(float('%.1f'%(float(size.strip('MB').strip('M'))/1024.0)))
    else:
        pass
    return size


def str_get_size(size):
    if not size:
        return 0
    if size.endswith('GB'):
        size = int(float(size[0:-2]) * 1024 * 1024)
    elif size.endswith('TB'):
        size = int(float(size[0:-2]) * 1024 * 1024 * 1024)
    elif size.endswith('MB'):
        size = int(float(size[0:-2]) * 1024)
    else:
        return 0 
    return size


def exception_pass(func, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception, e:
        traceback.print_exc()
        LOG.warn("%s WARNING: %s" % (func.func_name, e))
        return str(e)


def kwargs_get_id(kwargs):
    sid = kwargs.get('id')
    if not sid : 
        raise Exception('arguments must contain id')
    return sid 

    
def retains_decimal(val, decimal=0):
    prefix = "%.f" 
    if decimal > 0:
        prefix = "%.%sf" % decimal
    val = prefix % val
    return val

    
def percent(dividend, divisor, default=100, decimal=0):
    '''
        2/0 = default
        None/1 = default
        result type float 
    '''
    quotient = default
    if not dividend or not divisor:
        return default
    if int(divisor) != 0:
        quotient = float(dividend) / float(divisor) * 100
    quotient = retains_decimal(quotient)
    return quotient

#utils.update_values(values, 'mail', mail)
def update_values(values, k, v, is_list=False):
    if v is not None:
        if not v:
            if v == '':
                v = [] if is_list else None
        values[k] = v
    return values


def key_copy(res_dict, values, key):
    if values.get(key) is not None:
        res_dict[key] = values.get(key)
    return res_dict


def get_tid():
    import threading
    try:
        # Get thread id from thread local storage.
        # If not exist yet, get it via syscall().
        res = threading.local().threadid
    except AttributeError:
        import ctypes
        libc = ctypes.cdll.LoadLibrary('libc.so.6')
        SYS_gettid = 186
        res = threading.local().threadid = libc.syscall(SYS_gettid)
    return res


def split_lines(s):
    l = s.split('\n')
    res = [x.strip() for x in l if x.strip()]
    return res


def retry(retry_number=3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kw):
            attempt = 0
            while attempt < retry_number:
                try:
                    return func(self, *args, **kw)
                except Exception, e:
                    print e,'===='
                    attempt += 1
            raise 
        return wrapper
    return decorator


def except_run(exc_handler=None):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kw):
            try:
                return func(self, *args, **kw)
            except Exception, e:
                if exc_handler:
                    exc_handler(e, *args, **kw)
                else:
                    raise
        return wrapper
    return decorator


def timenow():
    return datetime.datetime.now()


def timedelta(**kw):
    return datetime.timedelta(**kw)


def parse_split(s, sep_char=' '):
    l = s.split(sep_char)
    res = [ x.strip() for x in l if x.strip()]
    return res


def str2date(s):
    ss = s.strip().split()
    ymd = ss[0]
    hms = ss[1] if len(ss) > 1 else ''
    hmses = parse_split(hms, ':')
    if len(hmses) < 3:
        [hmses.append('00') for i in range(3 - len(hmses))]
    hms = ":".join(hmses)
    s = " ".join([ymd, hms])

    return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S')


def compile_str(regexp, s):
    result_list = re.compile(r'%s' % regexp).findall(s)
    return ",".join(result_list)
